var searchTemplate = "<li id='{0}' data-loginname='{1}' class='ui-state-default search-user'>" +
"<div class='function handle'><i class='fa fa-arrows'></i></div>" +
"<img src='{3}'/><span class='name'>{2}</span></li>";
var itemTemplate = "<li id='{0}' data-loginname='{1}' class='ui-state-default'>" +
"<div class='function delete'><i class='fa fa-trash'></i></div>" +
"<div class='function handle'><i class='fa fa-arrows'></i></div>" +
"<img src='{3}'/><span class='name'>{2}</span></li>";
var defaultPicture = "/_layouts/15/IMAGES/PersonPlaceholder.96x96x32.png";

var isClone = false;
var isMove = true;
var isSearchAscending = true;
var requestMode = "";
var checkUnique = true;
var WorkItems = [];
var CancelItems = [];
var liUniqueKey = "loginname";
var jsonUniqueKey = "LoginName";
var appWebUrl = "";
var spHosted = false;
var hostWebUrl = "";
var searchGroupId = -1;
var sourceGroupId = -1;
var sourceGroupEditable = false;
var targetGroupId = -1;
var targetGroupEditable = false;
var sourceListId = "listA";
var targetListId = "listB";
var peoplePickerObj;
var sourceUsers = [];
var targetUsers = [];
var searchResultUsers = [];
var noSelectedGroupMsg = 'Please select a group to add user.';
var peoplePickerValidMsg = 'Click here to add user(s) into the group';
var emptySelectionMsg = "select or search a group to edit";
var emptyPeoplePickerMsg = "Please specify at least one user in the \"names or email addresses\" field.";
var sameSourceTargetError = "You have selected same group for both Source and Target. Please select other group.";
var bigGroupWarningMsg = "Too many members. To improve performance, filter the list by using 'Find'."
var noEditableGroupMsg = "You are not a group owner.";
var groupNoMemberMsg = "There are currently no members to display.";
var memberNoGroupMsg = "There are currently no groups to display."
var findMemberMsg = "find {length} existing member(s)";
var groupNameErrorMsg = 'A group name cannot contain any of the following characters: | \\ " \' / [ ] : > < + = , ; ? * @';
var ManageableGroups = [];
var UnviewableGroups = [];
var UnmanageableGroups = [];
var SiteMemberMenuItems = [
	{ 'action': 'view', 'text': 'View Profile', 'icon': 'info' },
	{ 'action': 'membership', 'text': 'Check Membership', 'icon': 'users' },
	{ 'action': 'delete', 'text': 'Remove from this Group', 'icon': 'times' },
	{
	    'action': 'deletemultiple', 'text': 'Remove Selected users from this Group - ({length})',
	    'icon': 'times', 'ismultiple': true
	}
];
var SearchUserMenuItems = [
	{ 'action': 'view', 'text': 'View Profile', 'icon': 'info' },
	{ 'action': 'membership', 'text': 'Check Membership', 'icon': 'users' },
	{ 'action': 'addtosearchgroup', 'text': 'Add this to the Selected group', 'icon': 'plus' },
	{
	    'action': 'addtosearchgroupmultiple', 'text': 'Add selected {length} users to the Selected group',
	    'icon': 'plus', 'ismultiple': true
	}
];

var _formDigestValue = "";
var _searchKeyIdle = 0;
var _searchkeyInternval = 1000;
var _searchKeyCheck;
var _logfilename = "Easy User Manager (replace .bin to .txt).bin";
var _reportJSON = [];
var _GROUP = 0, _WEB = 1, _LIST = 2;

function InitEasyApplication(suggestFullScreen) {
    hostWebUrl = getQueryStringParameter("SPHostUrl");
    appWebUrl = getQueryStringParameter("SPAppWebUrl");
    _formDigestValue = $("#__REQUESTDIGEST").val();
    if (typeof (hostWebUrl) != 'undefined') {
        spHosted = true;
        hostWebUrl = decodeURIComponent(hostWebUrl);
        appWebUrl = decodeURIComponent(appWebUrl);
    }
    else {
        if (typeof (_spPageContextInfo) != 'undefined') {
            appWebUrl = _spPageContextInfo.webAbsoluteUrl;
            hostWebUrl = _spPageContextInfo.webAbsoluteUrl;
        }
        else {
            appWebUrl = window.location.origin;
            hostWebUrl = window.location.origin;
        }
    }


    $("#source,#target,#searchGroup").autocomplete();

    //Initialize Context Menu Removal
    $("body").click(function () {
        $(".member-popup").fadeOut().remove();
    });

    //Check Full Screen
    if (!$('body').hasClass('ms-fullscreenmode') && suggestFullScreen) {
        ShowFullScreenSuggestion();
    }

    InitPeoplePicker();
    InitTabs();


    InitSearchPanel();
    GetSiteGroups();

}

function InitTabs() {
    $("#easy-tabs").find("li").click(function (e) {
        var tabId = $(this).data('tabid');
        ToggleTab(tabId);
    });

    var hash = window.location.hash.substr(1);
    if (hash && hash != "") {
        ToggleTab(hash);
    }
    else {
        ToggleTab('UserTab');
    }
}

function ToggleTab(tabId, obj) {
    $("div[data-tab]").not("[data-tab='" + tabId + "']").hide();
    $("[data-tab='" + tabId + "']").show();
    $("[data-tabid]").removeClass('active');
    $("[data-tabid='" + tabId + "']").addClass('active');

}

function InitSearchPanel() {
    $("#searchResultMode").buttonset();
    $("#searchResultSort").buttonset();
    SelectSearchResultMode();
}

function SelectSearchResultMode() {
    var isSearchListMode = document.getElementById("search_mode_list").checked == true;
    if (isSearchListMode) {
        $("#searchResult").addClass('search-list');
        $("#searchResult").removeClass('search-tile');
    }
    else {
        $("#searchResult").addClass('search-tile');
        $("#searchResult").removeClass('search-list');
    }
}

function SelectSearchResultSort() {
    isSearchAscending = document.getElementById("search_sort_asc").checked == true;
    _sortSearchResult();
    _generateSearchResultPanel();
}

function OnKeyDownSearchSiteInfoUser(event) {
    _searchKeyIdle = 0;
    var input = event.currentTarget;

    if (_searchKeyCheck) {
        _onKeyDownSearchSiteInfoUserReset();
    }

    if (event.keyCode == 13) {
        SearchSiteInfoUser(input);
    }
    else {
        _searchKeyCheck = setInterval(function () {
            _searchKeyIdle += 500;
            if (_searchKeyIdle++ >= _searchkeyInternval) {
                SearchSiteInfoUser(input);
                _onKeyDownSearchSiteInfoUserReset();
            }
        }, 500);
    }

}

function _onKeyDownSearchSiteInfoUserReset() {
    clearInterval(_searchKeyCheck);
    _searchKeyCheck = null;
}

function SearchSiteInfoUser(obj, silent) {
    silent = typeof (silent) !== 'undefined' ? silent : false;
    var term = escape($("#search_siteusers_input").val().trim());
    if (term == "") { return; }

    var restRelativeUrl = "/web/siteuserinfolist/items?$select=Id,Picture,Title,EMail,Name&$filter=(substringof('" + term + "',Title)" +
	" or substringof('" + term + "',EMail)) and (UserName ne null)";
    _makeSPSafeAjax(restRelativeUrl, {
        method: "GET",
        beforeSend: function () {
            _showLoadingIcon(obj);
            if (!silent) {
                _logConsole("Searching User with keyword '" + term + "'...");
            }
        },
        success: function (data) {
            if (data && data.d) {
                if (data.d.results.length > 0) {
                    searchResultUsers = data.d.results;
                }
                else {
                    searchResultUsers = [];
                }
                _sortSearchResult();
                _generateSearchResultPanel();
            }
        },
        error: errorHandler,
        complete: function () {
            _removeLoadingIcon(obj);
        }
    });
}

function _sortSearchResult() {
    if (isSearchAscending) {
        searchResultUsers.sort(function (a, b) {
            return a['Title'].toLowerCase() > b['Title'].toLowerCase() ? 1 : -1;
        });
    }
    else {
        searchResultUsers.sort(function (a, b) {
            return a['Title'].toLowerCase() <= b['Title'].toLowerCase() ? 1 : -1;
        });
    }
}

function InitSearchGroup(groupItems) {
    $("#searchGroup").autocomplete({
        source: groupItems,
        minLength: 0,
        select: function (event, ui) {
            event.preventDefault();
            var id = ui.item.value;

            var preValue = $(this).data().uiAutocomplete.term;
            if (preValue != ui.item.value) {
                var manageable = ManageableGroups.filter(function (a) { return a.Title == ui.item.label; });
                searchGroupId = id;
                $(this).val(ui.item.label);

            }
        },
        close: function (event, ui) {
            //revalidate (due to blue event where Select is not triggered
            if (event.target.value == "") {
                searchGroupId = -1;
            }


            var isEmpty = $(this).val() == "";
            if (isEmpty) {
                searchGroupId = -1;
            }
            else {
                $(this).data({ 'empty': false });
                $("#" + this.id + "_group").show();
            }
        }
    }).autocomplete().data("uiAutocomplete")._renderItem = function (ul, item) {
        var re = new RegExp(this.term, "gi");
        var t = item.label
        if (this.term != "") {
            t = item.label.replace(re, "<span style='font-weight:bold;'>" + this.term + "</span>");
        }
        return $("<li></li>").data("item.autocomplete", item).append("<a>" + t + "</a>").appendTo(ul);
    };


    $("#searchGroup").val(emptySelectionMsg).data({ 'empty': true }).click(function () {
        var isEmpty = $(this).data('empty') == true;
        if (isEmpty) {
            $(this).val("").trigger("focus").data({ 'empty': false });
            $(this).autocomplete("search", "");
        }
        $(this).select();
    }).keypress(function (event) {
        if (event.keyCode == 13) {
            event.preventDefault();
            return false;
        }
    });

    $("#searchResultGroup .comboclear").css('display', 'inline-block');
}

function DestroySortable() {
    if ($("#" + sourceListId).hasClass('ui-sortable')) {
        $("#" + sourceListId).sortable('destroy').find('.handle').hide();
    }
    if ($("#" + targetListId).hasClass('ui-sortable')) {
        $("#" + targetListId).sortable('destroy').find('.handle').hide();
    }

    _hideDragAndDropToggle();
}

function InitSortable() {
    $("#" + sourceListId + ",#" + targetListId).sortable({
        connectWith: ".sortable",
        handle: ".handle",
        helper: "clone",
        start: function (e, ui) {
            var isSelected = $(ui.item[0]).hasClass("ui-selected");
            _clearOtherSelection(this, e.target.id);

            if (isSelected) {
                var selectionInnerCount = $(e.currentTarget).find(".ui-selected:not(.ui-sortable-placeholder)").length
                if (selectionInnerCount > 2) {
                    var actualCount = selectionInnerCount - 2;
                    actualCount = actualCount > 4 ? 4 : actualCount;
                    var cloneHtml = ui.helper[0].outerHTML
                    var shadowHtml = "<div class='shadow'></div>";
                    var helperHtml = "";
                    var zIndex = $(cloneHtml).css('z-index');

                    $(e.target).find(".ui-selected").not(".ui-sortable-placeholder").not(".ui-sortable-helper").addClass('ui-multiselecting');

                    for (var i = 0; i < actualCount ; i++) {
                        helperHtml += $(shadowHtml).css({
                            top: (i * 3),
                            left: (i * 3)
                        })[0].outerHTML;
                    }

                    ui.helper.append(helperHtml);
                }
            }
            else {
                _clearOtherSelection(this);
            }
        },
        stop: function (e, ui) {
            if (isClone) {
                $(this).sortable('cancel');
            }
            else if (isMove) {
                if (requestMode == "CANCEL") {
                    $(this).sortable('cancel');
                }
                else if (requestMode == "MOVING") {
                    //Check if Duplicated items are not moved and to be added back to the source
                    if (CancelItems.length > 0) {
                        $(CancelItems).each(function (i, obj) {
                            $(e.target).append($(obj).addClass('ui-selected'));
                        })
                    }
                }
            }
            $(".ui-multiselecting").removeClass("ui-multiselecting");
            requestMode = "";
            InitSiteMembers();
        },
        beforeStop: function (e, ui) {
            if (e.target == ui.item.parent()[0]) {
                WorkItems = [];
                return;
            }

            var valid = true;
            if (isClone) {
                WorkItems = [ui.item.clone()];
            }
            else {
                WorkItems = [ui.item];
            }

            if ($(ui.item).hasClass('ui-multiselecting')) {
                $(e.target).find('.ui-multiselecting').not('.ui-sortable-helper').not('.ui-sortable-placeholder').each(function (i, obj) {
                    var workItem = isClone ? $(obj).clone() : $(obj);
                    WorkItems.push(workItem);
                })
            }

            //check unique
            if (checkUnique) {

                var existingItems = ui.item.parent()[0].id == sourceListId ? sourceUsers : targetUsers;
                var selectedArray = LIToArray(WorkItems);
                var sameItems = $(existingItems).filter(function (i, o) {
                    var id = o[jsonUniqueKey];
                    return selectedArray.indexOf(id) > -1;
                });
                if (sameItems.length > 0) {
                    if (sameItems.length >= selectedArray.length) {
                        var msg = "The selected item(s) has already existed in the destination list. No more item will be copied.";
                        CancelItems = WorkItems;
                        WorkItems = [];
                        requestMode = "CANCEL"
                        alert(msg);
                    }
                    else {
                        var msg = "The following item(s) has already existed in the destination list:\r" + DisplayUserLoginAsString(sameItems) + "\rClick Ok to proceed for the rest?";
                        if (confirm(msg)) {
                            var sameUserArray = UsersToArray(sameItems);
                            var itemsToBeProcessed = [];
                            var itemsToBeRetained = [];
                            $(WorkItems).each(function (i, o) {
                                var id = $(o).data(liUniqueKey);
                                if (sameUserArray.indexOf(id) == -1) {
                                    itemsToBeProcessed.push(o);
                                }
                                else {
                                    itemsToBeRetained.push(o);
                                }
                            });
                            WorkItems = itemsToBeProcessed;
                            CancelItems = itemsToBeRetained;
                        }
                        else {
                            requestMode = "CANCEL"
                            CancelItems = WorkItems;
                            WorkItems = [];
                        }
                    }
                }

            }
        },
        receive: function (e, ui) {
            //Or we can use REMOVE event to handle also
            if (WorkItems.length > 0) {
                if (isClone) {
                    if (confirm("Click OK to confirm Copying the user(s).")) {
                        requestMode = "COPYING";
                        $(WorkItems).each(function (i, obj) {
                            $(e.target).append(obj);
                        })

                        ProcessWorkItems(WorkItems, e.target.id);
                    }
                }
                else if (isMove) {
                    if (confirm("Click OK to confirm Moving the user(s).")) {
                        requestMode = "MOVING";
                        //replace the sortable dom and readd from the bottom
                        $(e.target).find(ui.item).remove();
                        $(WorkItems).each(function (i, obj) {
                            $(e.target).append(obj);
                        });

                        ProcessWorkItems(WorkItems, e.target.id);
                        _clearOtherSelection(this, e.target.id);
                    }
                    else {
                        requestMode = "CANCEL";
                    }
                }
            }

            //check if empty
            var childs = $(e.target).find('li');
            if (childs.length <= 0) {
                _showEmptyMembersMsg(e.target.id);
            }
            else {
                _removeEmptyMembersMsg(e.target.id);
            }

        },
        remove: function (e, ui) {
        },
        update: function (e, ui) {
        },
        over: function (e, ui) {
            var msg = isClone ? "Copy" : "Move";
            var parent = $(this).parent();
            var h = $(parent).outerHeight();
            var w = $(parent).outerWidth();
            var pos = $(parent).position();
            var top = pos.top;
            var left = pos.left;
            var hoverPanel = $("<div class='over-panel'></div>").css({
                'height': h, 'width': w,
                'top': top, 'left': left
            }).html(msg);

            $(parent).append(hoverPanel);
        },
        out: function (e, ui) {
            $(this).siblings('.over-panel').remove();
        }
    }).find('.handle').show();

    $("#listA, #listB").selectable({
        filter: "li",
        cancel: ".delete, .handle",
        selected: function (e, ui) {
        },
        selecting: function (e, ui) {
            _clearOtherSelection(this, e.target.id);
        },
        start: function (e, ui) {
        }
    });

    _showDragAndDropToggle()
}

function SelectDragMode() {
    isClone = document.getElementById("mode_copy").checked == true;
    isMove = document.getElementById("mode_move").checked == true;
}

function GetSortableListItems(target) {
    var filter = $(target).data('uiSelectable').options.filter;

    var ret = [];
    var itemsExceptDropped = $(target).find(filter).not(".ui-sortable-helper").not(".ui-sortable-placeholder").not(".ui-selected").each(function (i, obj) { ret.push(obj.id); });;

    return ret;
}

function LIToArray(items) {
    ret = [];
    $(items).each(function () { ret.push($(this).data(liUniqueKey)); });
    return ret;
}

function UsersToArray(users) {
    ret = [];
    $(users).each(function () { ret.push(this[jsonUniqueKey]); });
    return ret;
}

function DisplayUserLoginAsString(users) {
    var display = "";
    $(users).each(function () { display += this[jsonUniqueKey] + "\r"; });
    return display;
}

function ProcessWorkItems(workItems, uiId) {
    var fromGroupId = sourceGroupId;
    var toGroupId = targetGroupId;
    var fromId = sourceListId;
    var ToId = uiId;
    var fromUsers = sourceUsers;
    var toUsers = targetUsers;

    if (ToId == sourceListId) {
        fromGroupId = targetGroupId;
        toGroupId = sourceGroupId;
        fromId = targetListId;
        fromUsers = targetUsers;
        toUsers = sourceUsers;
    }

    $(workItems).each(function (i, obj) {
        var loginName = $(obj).data(liUniqueKey);
        var userObj = $(fromUsers).filter(function (i, obj) { return obj[jsonUniqueKey] == loginName; })
        _logConsole("Adding User ID:" + loginName + " to Group ID:" + toGroupId);
        AddUserToGroup(loginName, toGroupId, ToId);
        toUsers.push(userObj[0]);

    });

    if (ToId == sourceListId) { sourceUsers = toUsers }
    else { targetUsers = toUsers }


    if (requestMode == "MOVING") {
        //additionally remove the selected work item from the source group.
        $(workItems).each(function (i, obj) {
            var loginName = $(obj).data('loginname');
            _logConsole("Removing User ID:" + loginName + " from Group ID:" + fromGroupId);
            DeleteUserFromGroup(loginName, fromGroupId, fromId, true);
        });
    }
}

function GetSiteGroups() {
    _makeSPSafeAjax("/web/sitegroups?$select=Title,LoginName,Id,OwnerTitle,CanCurrentUserManageGroup,CanCurrentUserViewMembership", {
        method: "GET",
        beforeSend: function () {
            $("#source,#target,#searchGroup").autocomplete('disable').after('<i class="fa fa-spinner fa-spin"></i>');
        },
        success: function (data) {
            if (data.d && data.d.results) {
                LoadAjaxSiteGroup(data.d, "source");
                $("#source,#target,#searchGroup").removeAttr('disabled');
            }
        },
        error: errorHandler,
        complete: function () {
            $("#source,#target,#searchGroup").autocomplete('enable').next('.fa-spinner').remove();
        }
    });
}

function GetSiteGroupMembersForUI(uiId, groupId, silent) {
    silent = typeof (silent) !== 'undefined' ? silent : false;
    var restRelatvieUrl = "/web/sitegroups(" + groupId + ")/users";
    var targetName = uiId == sourceListId ? "Source" : " Target";
    _makeSPSafeAjax(restRelatvieUrl, {
        method: "GET",
        beforeSend: function () {
            if (!silent) {
                _logConsole("Loading Group ID:" + groupId + " to " + targetName + ".");
            }
            _showLoadingPanel(uiId);
        },
        success: function (data) {
            if (data.d && data.d.results) {

                LoadAjaxSiteGroupMember(data.d, groupId, uiId, silent);
            }
        },
        error: errorHandler,
        complete: function () {
            _removeLoadingPanel(uiId);
        }
    });
}

function OpenSourceSetting() {
    if (sourceGroupId < 0) {
        _logConsole('Please select a Source group.');
    }
    else {
        _openGroupSetting($("#source").val());
    }
}

function OpenTargetSetting() {
    if (targetGroupId < 0) {
        _logConsole('Please select a Target group.');
    }
    else {
        _openGroupSetting($("#target").val());
    }
}

function _openGroupSetting(groupName) {
    var dispUrl = appWebUrl + "/_layouts/15/editgrp.aspx?Group={0}".replace("{0}", escape(groupName));
    OpenPopUpPageWithDialogOptions({
        url: dispUrl
    });
}

function OpenSourcePermission() {
    if (sourceGroupId < 0) {
        _logConsole('Please select a Source group.');
    }
    else {
        _openGroupPermission(sourceGroupId);
    }
}

function OpenTargetPermission() {
    if (targetGroupId < 0) {
        _logConsole('Please select a Target group.');
    }
    else {
        _openGroupPermission(targetGroupId);
    }
}

function _openGroupPermission(groupId) {
    var dispUrl = hostWebUrl + "/_layouts/15/ViewGroupPermissions.aspx?ID={0}".replace("{0}", escape(groupId));
    window.open(dispUrl, "_blank", "width=650,height=500,modal=yes,dialog=yes");
    return false;
    OpenPopUpPageWithDialogOptions({
        url: dispUrl
    });
}


function RefreshSourceGroup() {
    if (sourceGroupId < 0) {
        _logConsole('Please select a Source group.');
    }
    else {
        GetSiteGroupMembersForUI(sourceListId, sourceGroupId, true);
        $("#search_listA").val("");
    }
}

function RefreshTargetGroup() {
    if (targetGroupId < 0) {
        _logConsole('Please select a Target group.');
    }
    else {
        GetSiteGroupMembersForUI(targetListId, targetGroupId, true);
        $("#search_listB").val("");
    }
}

function LoadAjaxSiteGroup(d) {
    var groupItems = []
    $(d.results).each(function (i, obj) {
        var title = obj.Title;
        var login = obj.LoginName;
        var id = obj.Id;
        if (obj.CanCurrentUserViewMembership !== true) {
            UnviewableGroups.push(obj);
        }
        else {
            groupItems.push({ label: title, value: id });
            if (obj.CanCurrentUserManageGroup === true) {
                ManageableGroups.push(obj);
            }
            else {
                UnmanageableGroups.push(obj);
            }
        }

    });
    InitSearchGroup(groupItems);
    InitSiteGroupCombo(groupItems);
    InitSiteGroupStats();

}


function LoadAjaxSiteGroupMember(d, groupId, listId, silent) {
    silent = typeof (silent) !== 'undefined' ? silent : false;
    $("#" + listId).html("loading...");

    $.when(_loadSiteUserProfile(d, silent)).done(function () {
        if (listId == sourceListId) {
            sourceUsers = d.results;
        }
        else if (listId == targetListId) {
            targetUsers = d.results;
        }

        _generateMemberPanel(d.results, listId);
        InitSiteMembers();
    })
}

function _loadSiteUserProfile(d, silent) {
    silent = typeof (silent) !== 'undefined' ? silent : false;
    var def = $.Deferred();
    var batchSize = 80;
    var startingIndex = 0;
    var batchCount = 1;
    var totalBatchCount = Math.ceil(d.results.length / batchSize);
    var tasks = [];
    while (d.results.length > startingIndex) {
        tasks.push(_loadSiteUserProfile2(d, silent, startingIndex, batchSize, batchCount, totalBatchCount));
        startingIndex += batchSize;
        batchCount++;
    }

    $.when.apply(null, tasks).done(function () {
        def.resolve();
    });
    return def.promise();
}

function _loadSiteUserProfile2(d, silent, from, size, batchCount, totalBatchCount) {
    silent = typeof (silent) !== 'undefined' ? silent : false;
    from = typeof (from) !== 'undefined' ? from : 0;
    from = from > d.results.length ? d.results.length : from;
    var maxSize = typeof (size) !== 'undefined' ? from + size : d.results.length;
    maxSize = maxSize > d.results.length ? d.results.length : maxSize;

    var ids = "";
    var tempStart = from;
    for (; from < maxSize; from++) {
        if (from !== tempStart) {
            ids += " or ";
        }
        var id = d.results[from].Id;
        d.results[from].PictureUrl = "";
        ids += "(Id eq " + id + ")";
    }
    var url = "/web/siteuserinfolist/items?$select=Id,Picture,Name&$filter=" + ids;
    var def = $.Deferred();
    $.when(_makeSPSafeAjax(url, {
        method: "GET",
        async: false,
        beforeSend: function () {
            if (!silent) {
                _logConsole("Loading User Profiles... (" + batchCount + "/" + totalBatchCount + ")");
            }
        },
        success: function (data) {
            if (data.d && data.d.results) {
                $(data.d.results).each(function (i, obj) {
                    if (obj.Picture && obj.Picture.Url) {
                        var instance = _findObjByAttr(d.results, "Id", obj.Id);
                        instance.PictureUrl = obj.Picture.Url;
                    }
                });

            }
        },
        error: errorHandler
    }))
    .done(function () {
        def.resolve();
    });

    return def.promise();
}

function InitSiteGroupCombo(groupItems) {

    if (groupItems.length <= 0) {
        $("#source,#target").val(noEditableGroupMsg);
        return;
    }

    $("#source,#target").autocomplete({
        source: groupItems,
        minLength: 0,
        select: function (event, ui) {
            event.preventDefault();

            var preValue = $(this).data().uiAutocomplete.term;
            if (preValue != ui.item.value) {
                var id = ui.item.value;

                //check if source and target are same id
                if ((this.id == "source" && targetGroupId > 0 && targetGroupId == id) ||
                (this.id == "target" && sourceGroupId > 0 && sourceGroupId == id)) {
                    alert(sameSourceTargetError);
                    if (this.id == "source") {
                        sourceGroupId = -1;
                        setTimeout(function () { $("#source").autocomplete("search", ""); }, 300);
                    }
                    else if (this.id == "target") {
                        targetGroupId = -1;
                        setTimeout(function () { $("#target").autocomplete("search", ""); }, 300);
                    }
                }
                else {
                    var manageable = ManageableGroups.filter(function (a) { return a.Title == ui.item.label; });
                    if (this.id == "source") {
                        sourceGroupId = id;
                        sourceGroupEditable = manageable.length >= 1;
                    }
                    else if (this.id == "target") {
                        targetGroupId = id;
                        targetGroupEditable = manageable.length >= 1;
                    }

                    var connectTo = $(this).data('connectto');

                    //$("#" + this.id + "_group").show();
                    GetSiteGroupMembersForUI(connectTo, id);
                    $(this).val(ui.item.label);



                }
            }
        },
        close: function (event, ui) {
            //revalidate (due to blue event where Select is not triggered
            if (event.target.value == "") {
                if (event.target.id == "target") {
                    targetGroupId = -1;
                }
                if (event.target.id == "source") {
                    sourceGroupId = -1;
                }
            }

            if ((targetGroupId > 0 && targetGroupEditable) && (sourceGroupId > 0 && sourceGroupEditable)) {
                InitSortable();
            }
            else {
                DestroySortable();
            }

            _checkAddMemberForms();

            var isEmpty = $(this).val() == "";
            if (isEmpty) {
                $(this).val(emptySelectionMsg).data({ 'empty': true });
                $("#" + this.id + "_group").hide();
                _hideComboFunctinoButton(this);
                if (this.id == "source") {
                    sourceGroupId = -1;
                }
                else if (this.id == "target") {
                    targetGroupId = -1;
                }
            }
            else {
                $(this).data({ 'empty': false });
                $("#" + this.id + "_group").show();
                var groupId = this.id == "source" ? sourceGroupId : this.id == "target" ? targetGroupId : -1;
                if (groupId > 0) {
                    _showComboFunctionButton(this);
                }
                else {
                    _logConsole("Error getting the group id upon menu close...");
                }
            }
        }
    }).autocomplete().data("uiAutocomplete")._renderItem = function (ul, item) {
        var re = new RegExp(this.term, "gi");
        var t = item.label
        if (this.term != "") {
            t = item.label.replace(re, "<span style='font-weight:bold;'>" + this.term + "</span>");
        }
        return $("<li></li>").data("item.autocomplete", item).append("<a>" + t + "</a>").appendTo(ul);
    };


    $("#source,#target").val(emptySelectionMsg).data({ 'empty': true }).click(function () {
        var isEmpty = $(this).data('empty') == true;
        if (isEmpty) {
            $(this).val("").trigger("focus").data({ 'empty': false });
            $(this).autocomplete("search", "");
        }
        $(this).select();
    }).keypress(function (event) {
        if (event.keyCode == 13) {
            event.preventDefault();
            return false;
        }
    });

}

function InitSiteGroupStats() {
    var uv_indicator = UnviewableGroups.length + " <i class='fa fa-eye-slash'></i>";
    $("#unviewable_indicator").html(uv_indicator).click(function () {
        var html = _generateGroupTable(UnviewableGroups);
        _openPopupWithHTML($(html)[0], "Group(s) in which you do not have view permission.");
    });

    var um_indicator = UnmanageableGroups.length + " <i class='fa fa-user-times'></i>";
    $("#unmanageable_indicator").html(um_indicator).click(function () {
        var html = _generateGroupTable(UnmanageableGroups);
        _openPopupWithHTML($(html)[0], "Group(s) in which you do not have edit permission.");
    });
}

function ClearCombo(listId) {
    $("#" + listId).val("").trigger("focus").autocomplete("search", "");
    $(this).hide();
}

function InitSiteMembers() {
    $("#listA").find('.delete').unbind('click').bind('click', function () {
        var loginname = $(this).parent().data('loginname');
        DeleteUserFromGroup(loginname, sourceGroupId, "listA");
    });

    $("#listB").find('.delete').unbind('click').bind('click', function () {
        var loginname = $(this).parent().data('loginname');
        DeleteUserFromGroup(loginname, targetGroupId, "listB");
    });

    if ($("#listB").hasClass('ui-sortable')) {
        $("#listB").find('.handle').show();
    }

    if ($("#listA").hasClass('ui-sortable')) {
        $("#listA").find('.handle').show();
    }

    $("#listA, #listB").selectable({
        filter: "li",
        cancel: ".delete, .handle",
        selected: function (e, ui) {
        },
        selecting: function (e, ui) {
            _clearOtherSelection(this, e.target.id);
        },
        start: function (e, ui) {
        }
    });

    //Reinitialize Context Menu base on the members
    _initSiteMembersContextMenu()

    //ui enhance
    var sourceFindMsgText = findMemberMsg.replace('{length}', sourceUsers.length);
    $("#search_listA").attr('placeholder', sourceFindMsgText);

    var targetFindMsgText = findMemberMsg.replace('{length}', targetUsers.length);
    $("#search_listB").attr('placeholder', targetFindMsgText);

    //Health Check
    $(".dragcontainer .text-danger").remove();
    if (sourceUsers.length > 100 || targetUsers.length > 100) {
        $(".dragcontainer").append("<p class='text-danger'>" + bigGroupWarningMsg + "</p>");
    }
}

function AddUserToGroup(userLogin, groupId, uiId, async, successCallback) {
    async = typeof (async) !== 'undefined' ? async : true;
    var def = $.Deferred();
    var url = appWebUrl + "/_api/web/sitegroups(" + groupId + ")/users";
    var slashedLoginName = userLogin.replace(/\\+/g, "\\\\");
    _makeSPSafeAjax("/web/sitegroups(" + groupId + ")/users", {
        method: "POST",
        data: "{ '__metadata': { 'type': 'SP.User' }, 'LoginName':'" + slashedLoginName + "' }",
        beforeSend: function () {
            _showLoadingPanel(uiId);
        },
        success: function (data) {
            if (data.d) {
                _logConsole("Added User " + userLogin + " to Group ID:" + groupId);
                if (typeof (successCallback) == 'function') {
                    successCallback();
                }
            }
        },
        error: errorHandler,
        complete: function () {
            _removeLoadingPanel(uiId);
            def.resolve();
        }
    });

    return def.promise();
}

function DeleteUserFromGroup(userLogin, groupId, uiId, silent) {
    //With UI Assumption that $(this) == DIV parent is 'LI'
    if (userLogin) {
        if (silent || confirm('Confirm to delete this user? ' + userLogin)) {
            var escapedloginname = escape(userLogin);
            var slashedLoginName = escapedloginname.replace(/\\+/g, "\\\\");
            var restfulUrl = "/web/sitegroups(" + groupId + ")/users/removebyloginname(@v)?@v='"
            + slashedLoginName + "'";
            var def = $.Deferred();

            _makeSPSafeAjax(restfulUrl, {
                method: "POST",
                beforeSend: function () {
                    _showLoadingPanel(uiId);
                },
                success: function (data) {
                    _logConsole("Removed User:" + userLogin + " from Group ID:" + groupId);

                    //Remove UI
                    var escapeSelector = userLogin.replace(/[!"#$%&'()*+,.\/:;<=>?@[\\\]^`{|}~]/g, "\\$&");
                    $("#" + uiId).find("li[data-loginname='" + escapeSelector + "']").remove();

                    //Remove Data Array
                    if (uiId == sourceListId) {
                        sourceUsers = $(sourceUsers).filter(function (i, obj) { return obj.LoginName != userLogin; })
                    }
                    else {
                        targetUsers = $(targetUsers).filter(function (i, obj) { return obj.LoginName != userLogin; })
                    }
                },
                error: errorHandler,
                complete: function () {
                    _removeLoadingPanel(uiId);
                    def.resolve();
                }
            });
        }
    }
    return def.promise();
}

function ViewUser(userId) {
    var dispUrl = hostWebUrl + "/_layouts/15/userdisp.aspx?ID={userId}".replace('{userId}', userId);
    if (spHosted) {
        //Prevent IFrame Same Origin policy
        window.onbeforeunload = function confirmExit() {
            return "You have attempted to leave this page. Are you sure?";
        }
        window.top.location = dispUrl;
    }
    else {
        OpenPopUpPageWithDialogOptions({
            url: dispUrl
        });
    }
}

function OpenCreateGroupWindow() {
    var template = "<p>Name </br><input type='text' maxlength='255' size='50' name='groupname' onkeyup='_creteAGroupKeyUp(event);'></input></br><span id='cgError'></span></p>" +
		"<div style='text-align:center'>" +
		"<button onclick='CreateAGroup();'>Create</button>" +
		"<button onclick='SP.UI.ModalDialog.commonModalDialogClose(0);'>Cancel</button></div>";
    var html = document.createElement('div');
    html.innerHTML = template;
    OpenPopUpPageWithDialogOptions({
        title: "Create a group",
        html: html,
        showClose: true,
        height: 120,
        width: 400
    });
}
function _creteAGroupKeyUp(e) { if (e.keyCode == 13) { CreateAGroup(); } }
function CreateAGroup() {
    var postUrl = "/web/sitegroups";
    var title = $("input[name='groupname']").val();

    var val = $.trim(title);
    var sInvalidChars = "|\"'\\/[]:<>+=,;?*@";
    var index = 0;
    var isValid = true;
    while (index < val.length) {
        if (sInvalidChars.indexOf(val.charAt(index)) != -1) {
            isValid = false;
            break;
        }
        index++;
    }
    if (!isValid) {
        $('#cgError').text(groupNameErrorMsg);
    }
    else {
        $('#cgError').text();
        _logConsole("Creating Group \"" + title + "\"");
        _makeSPSafeAjax(postUrl, {
            method: "POST",
            data: "{ '__metadata': { 'type': 'SP.Group' }, 'Title':'" + title + "' }",
            beforeSend: function () {
                waitingDialog = SP.UI.ModalDialog.showWaitScreenWithNoClose("Creating...", "this may take a while ...", 150, 400);
            },
            success: function (data) {
                console.log(data);
                if (data.d && data.d.Id) {
                    GetSiteGroups();
                    _logConsole("Successfully created group \"" + title + "\" Id:" + data.d.Id);
                    alert("Created group \"" + data.d.Title + "\" successfully.");
                }
            },
            error: errorHandler,
            complete: function () {
                waitingDialog.close();
                SP.UI.ModalDialog.commonModalDialogClose(0);
            }
        });
    }
}

function SearchClientSiteGroupMembers(listId, term) {
    var matchesUsers = [];
    if (listId == sourceListId) {
        matchesUsers = _searchUsersByTerm(sourceUsers, term);
    }
    else {
        matchesUsers = _searchUsersByTerm(targetUsers, term);
    }

    _generateMemberPanel(matchesUsers, listId);
    InitSiteMembers();
}

function _searchUsersByTerm(users, term) {
    var matches = [];
    var contains = new RegExp(term, "i")
    matches = $(users).filter(function (i, obj) {
        return contains.test(obj.Title);
    });
    return matches;
}

function _getClientUsersById(uiId, loginName) {
    var matches;
    if (uiId == sourceListId) {
        matches = $(sourceUsers).filter(function (i, obj) { return obj.Id == loginName; })
    }
    else if (uiId == targetListId) {
        matches = $(targetUsers).filter(function (i, obj) { return obj.Id == loginName; })
    }
    else {
        matches = $(searchResultUsers).filter(function (i, obj) { return obj.Id == loginName; })
    }
    return matches[0];
}

/******** Internal UI function *********/
/***************************************/
function _generateSearchResultPanel() {
    var searchDom = "";
    $(searchResultUsers).each(function (i, obj) {
        var title = obj.Title;
        var login = obj.Name;
        var id = obj.Id;
        var pictureUrl = (obj.Picture && obj.Picture.Url) ? obj.Picture.Url : defaultPicture;
        searchDom += searchTemplate.replace('{0}', id).replace('{1}', login).replace(/\{2\}/g, title).replace('{3}', pictureUrl);
    });

    $("#searchResult").html(searchDom);
    if (searchResultUsers.length > 0) {
        $("#searchResult").parent().find('.emptymessage').remove();
        $("#searchResult, #searchResultMode, #searchResultSort, #searchResultInfo, #searchResultGroup").show();

        $("#searchResult").selectable({
            filter: "li",
            selected: function (e, ui) {
            },
            selecting: function (e, ui) {
                _clearOtherSelection(this, e.target.id);
            },
            start: function (e, ui) {
            }
        });
        _initSearchContextMenu();

    }
    else {
        $("#searchResult").parent().find('.emptymessage').remove();
        $("#searchResult").before("<p class='emptymessage'>No User was found...</p>");
        $("#searchResult, #searchResultMode, #searchResultSort, #searchResultInfo, #searchResultGroup").hide();
    }
}

function _showDragAndDropToggle() {
    $("#dragAndDropMode").show().buttonset().disableSelection().find('label').removeClass('ui-corner-right ui-corner-left');
    $("#dragAndDropInfo").show();
}

function _hideDragAndDropToggle() {
    $("#dragAndDropMode").hide();
    $("#dragAndDropInfo").hide();
}

function _clearOtherSelection(obj, currentId) {
    var filter = $(obj).data('uiSelectable').options.filter;
    $(".selectable[ID!='" + currentId + "'] " + filter).removeClass("ui-selected");
}

function _showComboFunctionButton(combo) {
    //Show Refresh function
    //Show Clear Text function
    //Show Search function
    $(combo).parents('.groupcontainer').find('.groupsetting, .grouprefresh, .comboclear, .groupsearch, .grouppermission')
    .css('display', 'inline-block').tooltip({
        position: { my: "center bottom", at: "center top" }
    });
}

function _hideComboFunctinoButton(combo) {
    $(combo).parents('.groupcontainer').find('.groupsetting, .grouprefresh, .comboclear, .groupsearch, .grouppermission').hide();
}

function _showEmptyMembersMsg(uiId) {
    _removeEmptyMembersMsg(uiId);
    $("#" + uiId).before("<div class='emptymessage'>" + groupNoMemberMsg + "</div>");
}
function _removeEmptyMembersMsg(uiId) {
    $("#" + uiId).prev(".emptymessage").remove();
}

function _showLoadingPanel(uiId) {
    var parent = $("#" + uiId).parent();
    if (!$(parent).is(":visible")) {
        pos = $(parent).show();
    }

    var h = $(parent).outerHeight();
    var w = $(parent).outerWidth();
    var pos = $(parent).position();
    var top = pos.top;
    var left = pos.left;

    if (!$(parent).is(":visible")) {
        $(parent).hide();
    }

    var loader = $("<div id='loadingPanel'></div>").css({
        'height': h, 'width': w,
        'top': top, 'left': left
    });
    $(parent).append(loader);

}

function _removeLoadingPanel(uiId) {
    $("#" + uiId).parent().find("#loadingPanel").remove();;
}

function _showLoadingIcon(obj) {
    var loader = $("<i id='loadingIcon' class='fa fa-spinner fa-spin'></i>");
    $(obj).after(loader);
}

function _removeLoadingIcon(obj) {
    $(obj).next("#loadingIcon").remove();
}

function _openPopupWithHTML(html, title, callback) {
    var calloutHtml = html;
    if (typeof (html) == "string" || typeof (html) == "undefined") {
        calloutHtml = document.createElement('html');
        calloutHtml.innerHTML = html;
        calloutHtml.className = "ms-dialog";
    }

    OpenPopUpPageWithDialogOptions({
        html: calloutHtml,
        title: title,
        showClose: true,
        autoSize: true,
        height: 450,
        width: 500,
        dialogReturnValueCallback: callback
    });
}

function _generateMemberPanel(users, listId) {
    var groupDom = "";
    $(users).each(function (i, obj) {
        var title = obj.Title;
        var login = obj.LoginName;
        var id = obj.Id;
        var pictureUrl = obj.PictureUrl == "" ? defaultPicture : obj.PictureUrl;
        groupDom += itemTemplate.replace('{0}', id).replace('{1}', login).replace(/\{2\}/g, title).replace('{3}', pictureUrl);
    });

    if (groupDom === "") {
        _showEmptyMembersMsg(listId);
    }
    else {
        _removeEmptyMembersMsg(listId);
    }
    $("#" + listId).html(groupDom);

    if ((listId == sourceListId && !sourceGroupEditable) || (listId == targetListId && !targetGroupEditable)) {
        $("#" + listId).find(".function").hide();
    }
}

function _generateGroupTable(groups) {
    var header = "<div class='callout'><div class='row'>" +
    "<div class='callout-header td1'>Group</div><div class='callout-header td2'>Owner</div>" +
    "</div></div>";
    header = "<thead><tr><th>Group</th><th>Owner</th></tr></thead>";
    var footer = "<tfoot></tfoot>";
    var body = "<tbody></tbody>";
    var table = "<table id='dttable' class='callout-table'></table>";

    var script = document.createElement('script');
    script.type = "text/javascript";

    //refer http://datatables.net/examples/basic_init/dom.html for dom positioning
    var dom = '<"callout-header"p><"callout-body"rt>';
    script.text = "$(document).ready(function() {$('#dttable').DataTable({'dom':'" + dom + "'});} );";

    if (groups == null || groups.length < 1) {
        return $("<div class='emptymessage'>" + memberNoGroupMsg + "</div>");
    }

    table = $(table).append(header);

    $(groups).each(function (i, obj) {
        body = $(body).append("<tr><td>" +
        obj.Title + "</td><td>" + obj.OwnerTitle + "</td></tr>");
    });
    table = $(table).append(body);
    table = $(table).append(footer);
    table = $(table).append(script);
    return table;
}

function _logConsole(msg) {
    $("#console").html($("#console").html() + msg + "<br>");
}


function _checkAddMemberForms() {
    var resolvedUsers = peoplePickerObj.GetAllUserKeys();
    if (resolvedUsers.length > 0) {
        if (sourceGroupId > 0) {
            $("#add_to_source").removeAttr('disabled');
            $("#add_to_source").attr('title', peoplePickerValidMsg);
        }
        else {
            $("#add_to_source").attr('disabled', 'disabled');
            $("#add_to_source").attr('title', noSelectedGroupMsg);
        }

        if (targetGroupId > 0) {
            $("#add_to_target").removeAttr('disabled');
            $("#add_to_target").attr('title', peoplePickerValidMsg);
        }
        else {
            $("#add_to_target").attr('disabled', 'disabled');
            $("#add_to_target").attr('title', noSelectedGroupMsg);
        }

        if (targetGroupId <= 0 && sourceGroupId <= 0) {
            $("#peoplePickerMsg").html(noSelectedGroupMsg);
        }
        else {
            $("#peoplePickerMsg").html("");
        }
    }
    else {
        $("#add_to_source,#add_to_target").attr('disabled', 'disabled');
        $("#peoplePickerMsg").html(emptyPeoplePickerMsg);
    }
}

function _findObjByAttr(arr, attr, val) {
    for (var i = 0; i < arr.length; i += 1) {
        if (arr[i][attr] === val) {
            return arr[i];
        }
    }
}

function _makeSPSafeAjax(relativeUrl, ajaxOption) {
    var def = null;
    if (ajaxOption.async === false) {
        def = $.Deferred();
        ajaxOption.async = true;
        //set true and to use Deferred to control
    }

    //Always override
    if (typeof (ajaxOption.headers) == "undefined") {
        ajaxOption.headers = {
            "accept": "application/json; odata=verbose",
            "content-type": "application/json; odata=verbose",
        }
    }
    else {
        ajaxOption.headers["accept"] = "application/json; odata=verbose";
        ajaxOption.headers["content-type"] = "application/json; odata=verbose";
    }



    if (ajaxOption.method != "GET" && !spHosted) {
        ajaxOption.headers["X-RequestDigest"] = _formDigestValue;
    }


    if (spHosted) {

        relativeUrl = relativeUrl.indexOf('?') > 0 ?
        relativeUrl.split('?')[0] + "?@target='{0}'&".replace('{0}', hostWebUrl) + relativeUrl.split('?')[1]
        : relativeUrl + "?@target='{0}'".replace('{0}', hostWebUrl);

        ajaxOption.url = appWebUrl + "/_api/SP.AppContextSite(@target)" + relativeUrl;

        ajaxOption.body = typeof (ajaxOption.bod) == 'undefined' ? ajaxOption.data : ajaxOption.body;
        $.getScript(hostWebUrl + "/_layouts/15/SP.RequestExecutor.js", function () {
            var executor = new SP.RequestExecutor(appWebUrl);

            if (typeof (ajaxOption.beforeSend) == 'function') {
                ajaxOption.beforeSend();
            }

            var oriSuccess = ajaxOption.success;
            ajaxOption.success = function (data) {
                var parsedJSON = JSON.parse(data.body);
                oriSuccess(parsedJSON);

                if (typeof (ajaxOption.complete) == 'function') {
                    ajaxOption.complete();
                }

                if (def !== null) {
                    def.resolve(parsedJSON);
                }
            };

            var oriError = ajaxOption.error;
            ajaxOption.error = function (data, errorCode, errorMessage) {
                if (typeof (oriError) == 'function') {
                    oriError(data, errorCode, errorMessage);
                }
                if (typeof (ajaxOption.complete) == 'function') {
                    ajaxOption.complete(data, errorCode, errorMessage);
                }
                if (def !== null) {
                    def.reject(data + errorMessage);
                }
            };
            executor.executeAsync(ajaxOption);
        });


    }
    else {
        ajaxOption.url = appWebUrl + "/_api" + relativeUrl;
        $.ajax(ajaxOption).done(function () {
            if (def !== null) {
                def.resolve();
            }
        });
    }

    if (def !== null) {
        return def.promise();
    }
}

function _makeDigestCall(callback, async, obj) {
    async = typeof (async) !== 'undefined' ? async : true;
    if (_formDigestValue == "") {
        _formDisgestValue = $("#__REQUESTDIGEST").val();
    }

    if (_formDigestValue != "") {
        if (typeof (callback) == "function") {
            callback();
        }
    }
    else {
        $.ajax({
            url: appWebUrl + "/_api/contextinfo",
            method: "POST",
            async: async,
            headers: { "accept": "application/json; odata=verbose" },
            beforeSend: function () {
                if (typeof (obj) == 'object') {
                    _showLoadingIcon(obj);
                }
                else if (typeof (obj) == 'string') {
                    var dom = $("#" + obj);
                    if (dom.length > 0) {
                        _showLoadingIcon(dom[0]);
                    }
                }
            },
            success: function (data) {
                _formDigestValue = data.d.GetContextWebInformation.FormDigestValue
                if (typeof (callback) == "function") {
                    callback();
                }
            },
            complete: function () {
                if (typeof (obj) == 'object') {
                    _removeLoadingIcon(obj);
                }
                else if (typeof (obj) == 'string') {
                    var dom = $("#" + obj);
                    if (dom.length > 0) {
                        _removeLoadingIcon(dom[0]);
                    }
                }
            },
            error: errorHandler
        });
    }
}

function InitPeoplePicker() {
    var elementId = 'peoplePickerDiv';
    var schema = {};
    schema['PrincipalAccountType'] = 'User,DL,SecGroup,SPGroup';
    schema['SearchPrincipalSource'] = 15;
    schema['ResolvePrincipalSource'] = 15;
    schema['AllowMultipleValues'] = true;
    schema['MaximumEntitySuggestions'] = 50;
    schema['Width'] = '400px';
    schema['ShowUserPresence'] = true;

    SPClientPeoplePicker.InitializeStandalonePeoplePicker(elementId, null, schema);
    var clientId = elementId + "_TopSpan";
    peoplePickerObj = this.SPClientPeoplePicker.SPClientPeoplePickerDict[clientId];
    peoplePickerObj.OnUserResolvedClientScript = function () { _checkAddMemberForms() };
}


function AddToSource_Click() {
    _addTo_Click(sourceGroupId, sourceListId);
}

function AddToTarget_Click() {
    _addTo_Click(targetGroupId, targetListId);
}

function _addTo_Click(groupId, uiId) {
    var users = peoplePickerObj.GetAllUserKeys().split(';');
    var displayUserString;
    var proceed = true;
    var existingItems = uiId == sourceListId ? sourceUsers : targetUsers;
    var selectedArray = users;
    var workItems = [];
    var sameItems = $(existingItems).filter(function (i, o) {
        var id = o[jsonUniqueKey];
        return selectedArray.indexOf(id) > -1;
    });

    if (sameItems.length > 0) {
        if (sameItems.length >= selectedArray.length) {
            var msg = "The selected user(s) has already existed in the destination group. No more user will be added.";
            alert(msg);
            proceed = false;
        }
        else {
            var sameUserArray = UsersToArray(sameItems);
            var msg = "The following user(s) has already existed in the destination group:\r" +
            DisplayUserLoginAsString(sameItems) + "\rClick Ok to proceed for the rest?";
            proceed = confirm(msg);
            if (proceed) {
                $(selectedArray).each(function (i, obj) {
                    var id = obj;
                    if (sameItemArray.indexOf(id) < 0) {
                        workItems.push(obj);
                    }

                });

            }
        }
    }
    else {
        workItems = selectedArray;
    }

    if (proceed && workItems.length > 0) {
        var tasks = [];
        $(workItems).each(function (i, obj) {
            var loginName = obj;
            _logConsole("Adding User ID:" + loginName + " to Group ID:" + groupId);
            tasks.push(AddUserToGroup(loginName, groupId, uiId, false));
        });

        //Refresh Member Panel
        $.when.apply(null, tasks).done(function () {
            if (uiId == sourceListId) { RefreshSourceGroup(); }
            if (uiId == targetListId) { RefreshTargetGroup(); }
        });

    }
}


/******** Context Menu *********/
/******************************/
function _initSiteMembersContextMenu() {
    $("#listA,#listB").find("li").bind('contextmenu', function (e) {
        var userId = this.id;
        var listId = $(this).parent()[0].id;

        $(".member-popup").remove();
        var contextMenu = $("<div class='member-popup'></div>").click(function (e) {
            //read object
            e.stopPropagation();
        }).css({
            'top': e.clientY,
            'left': e.clientX
        }).attr({
            'id': userId + "_contextmenu",
            'data-userid': userId,
            'data-listid': listId
        }).disableSelection();

        //Render Menu Item
        var selectedLIs = $(e.currentTarget).parents('ul').find(".ui-selected");
        _prepContextMenuItem(contextMenu, SiteMemberMenuItems, selectedLIs);

        //Append to Body
        $("body").append($(contextMenu).fadeIn());
        return false;
    });
}

function _initSearchContextMenu() {
    $("#searchResult").find("li").bind('contextmenu', function (e) {
        var userId = this.id;
        var listId = $(this).parent()[0].id;

        $(".member-popup").remove();
        var contextMenu = $("<div class='member-popup'></div>").click(function (e) {
            //read object
            e.stopPropagation();
        }).css({
            'top': e.clientY,
            'left': e.clientX
        }).attr({
            'id': userId + "_contextmenu",
            'data-userid': userId,
            'data-listid': listId
        }).disableSelection();

        //Render Menu Item
        var selectedLIs = $("#searchResult").find(".ui-selected");
        _prepContextMenuItem(contextMenu, SearchUserMenuItems, selectedLIs);

        //Append to Body
        $("body").append($(contextMenu).fadeIn());
        return false;
    });
}

function _prepContextMenuItem(contextMenu, menuItemTemplates, multiSelections) {
    $(menuItemTemplates).each(function (i, obj) {
        var menuMultiple = typeof (obj.ismultiple) != 'undefined' && obj.ismultiple;
        if (menuMultiple && (multiSelections == null || multiSelections.length < 1)) {
            return;
        }
        var menuAction = obj.action;
        var menuText = menuMultiple ? obj.text.replace('{length}', multiSelections.length) : obj.text;
        var menuFAIcon = "<i class='fa fa-" + obj.icon + " icon'></i>";
        var menuTextDiv = "<div class='menu-item-label'>" + menuText + "</div>"
        var menuItem = $("<div class='menu-item' data-action='" + menuAction + "'>" + menuFAIcon + menuTextDiv + "</div>")
        .bind('click', _contextmenu_click).bind('contextmenu', function () { return false; });

        if (menuAction == "addtosearchgroup" && searchGroupId <= 0) {
            $(menuItem).addClass('disabled').attr('title', noSelectedGroupMsg).unbind('click');
        }
        if (menuAction == "addtosource" && sourceGroupId <= 0) {
            $(menuItem).addClass('disabled').attr('title', noSelectedGroupMsg).unbind('click');
        }
        if (menuAction == "addtotarget" && targetGroupId <= 0) {
            $(menuItem).addClass('disabled').attr('title', noSelectedGroupMsg).unbind('click');
        }
        if (menuAction == "addtosearchgroupmultiple" && searchGroupId <= 0) {
            $(menuItem).addClass('disabled').unbind('click');
        }
        if (menuAction == "addtosourcemultiple" && sourceGroupId <= 0) {
            $(menuItem).addClass('disabled').unbind('click');
        }
        if (menuAction == "addtotargetmultiple" && targetGroupId <= 0) {
            $(menuItem).addClass('disabled').unbind('click');
        }

        $(contextMenu).append(menuItem);
    });
    return contextMenu;
}

function _contextmenu_click(e) {
    var action = $(this).data("action");
    var uiId = $(this).parent().data("listid");
    var userId = $(this).parent().data("userid");
    var userObj = _getClientUsersById(uiId, userId);
    var userLoginName = userObj.LoginName ? userObj.LoginName : userObj.Name;

    switch (action) {
        //Check Membership
        case "membership":
            {
                GetSiteGroupsByUserId(userObj.Id, userObj.Title, userLoginName, uiId);
                break;
            }
            //Delete User from this Group
        case "delete":
            {
                var groupId = -1;
                if (uiId == sourceListId) {
                    groupId = sourceGroupId;
                }
                else {
                    groupId = targetGroupId;
                }
                DeleteUserFromGroup(userLoginName, groupId, uiId);
                break;
            }
        case "deletemultiple":
            {
                var groupId = -1;
                var fromUsers = [];
                if (uiId == sourceListId) {
                    groupId = sourceGroupId;
                    fromUsers = sourceUsers;
                }
                else {
                    groupId = targetGroupId;
                    fromUsers = targetUsers;
                }
                var selectedLis = $("#" + uiId).find('.ui-selected');
                var selectedNames = LIToArray(selectedLis).toString().replace(/,/g, "\r");
                if (confirm('Confirm to delete the follwing user(s)?\r' + selectedNames + "\r\rClick OK to proceed.")) {
                    $(selectedLis).each(function (i, obj) {
                        var loginName = $(obj).data(liUniqueKey);
                        DeleteUserFromGroup(loginName, groupId, uiId, true);
                    });
                }
                break;
            }

        case "addtosearchgroup":
            {
                var groupName = $("#searchGroup").val();
                if (confirm("Confirm to add this user to " + groupName + "?")) {
                    $.when(AddUserToGroup(userLoginName, searchGroupId, uiId, false));
                }
                break;
            }
            //Add to Source Group
        case "addtosource":
            {
                var groupName = $("#source").val();
                if (confirm("Confirm to add this user to " + groupName + "?")) {
                    $.when(AddUserToGroup(userLoginName, sourceGroupId, uiId, false))
                    .done(RefreshSourceGroup);
                }
                break;
            }
            //Add to Target Group
        case "addtotarget":
            {
                var groupName = $("#target").val();
                if (confirm("Confirm to add this user to " + groupName + "?")) {
                    $.when(AddUserToGroup(userLoginName, targetGroupId, uiId, false))
                    .done(RefreshTargetGroup);
                }
                break;
            }
            //View Profile
        case "view":
            {
                ViewUser(userId);
                break;
            }
        case "addtosearchgroupmultiple":
            {
                var searchGroupName = $("#searchGroup").val();
                var selectedLis = $("#" + uiId).find('.ui-selected');
                var selectedNames = LIToArray(selectedLis).toString().replace(/,/g, "\r");
                if (confirm("Confirm to add the following users to " + searchGroupName + "?\r" + selectedNames + "\r\rClick Ok to proceed")) {
                    var tasks = [];
                    $(selectedLis).each(function (i, obj) {
                        var loginName = $(obj).data(liUniqueKey);
                        tasks.push(AddUserToGroup(loginName, searchGroupId, uiId, false));
                    });

                    $.when.apply(null, tasks).done(function () {
                        alert("Added users successfully.");
                    });
                }
                break;
            }
        case "addtosourcemultiple":
            {
                var sourceGroupName = $("#source").val();
                var selectedLis = $("#" + uiId).find('.ui-selected');
                var selectedNames = LIToArray(selectedLis).toString().replace(/,/g, "\r");
                if (confirm("Confirm to add the following users to " + sourceGroupName + "?\r" + selectedNames + "\r\rClick Ok to proceed")) {
                    var tasks = [];
                    $(selectedLis).each(function (i, obj) {
                        var loginName = $(obj).data(liUniqueKey);
                        tasks.push(AddUserToGroup(loginName, sourceGroupId, uiId, false));
                    });

                    $.when.apply(null, tasks).done(function () {
                        RefreshSourceGroup();
                        alert("Added users successfully.");
                    });
                }
                break;
            }
        case "addtotargetmultiple":
            {
                var targetGroupName = $("#target").val();
                var selectedLis = $("#" + uiId).find('.ui-selected');
                var selectedNames = LIToArray(selectedLis).toString().replace(/,/g, "\r");
                if (confirm("Confirm to add the following users to " + targetGroupName + "?\r" + selectedNames + "\r\rClick Ok to proceed")) {
                    var tasks = [];
                    $(selectedLis).each(function (i, obj) {
                        var loginName = $(obj).data(liUniqueKey);
                        tasks.push(AddUserToGroup(loginName, targetGroupId, uiId, false));
                    });

                    $.when.apply(null, tasks).done(function () {
                        RefreshTargetGroup();
                        alert("Added users successfully.");
                    });

                }
                break;
            }
        default:
            {
                console.log('Action not implemented');
            }
    }

    $(".member-popup").remove();
}



//Obsoleted
function DownloadLogs() {
    return;
    var logs = $("#console").html().trim().replace(/\<br\>/g, "\r\n");
    var bytes = new Uint8Array(logs.length);
    for (var i = 0; i < logs.length; i++) {
        bytes[i] = logs.charCodeAt(i);
    }

    window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;

    window.requestFileSystem(window.TEMPORARY, 1024 * 1024, function (fs) {
        fs.root.getFile(_logfilename, { create: true }, function (fileEntry) {
            fileEntry.createWriter(function (fileWriter) {

                var blob = new Blob([bytes]);

                fileWriter.addEventListener("writeend", function () {
                    // navigate to file, will download
                    location.href = fileEntry.toURL();
                }, false);

                fileWriter.write(blob);
            }, function () { });
        }, function () { });
    }, function () { });
}

function DownloadReport(type) {
    var reportType = typeof type != 'undefined' ? type : 0;
    var waitingDialog;
    var reportUrl = "";
    var reportFileName = "";
    switch (type) {
        case _GROUP:
            reportUrl = "/web/sitegroups?$select=Title,LoginName,Id,OwnerTitle,CanCurrentUserManageGroup,CanCurrentUserViewMembership,\
	Users&$expand=Users&$sort=Title&$filter=CanCurrentUserViewMembership eq true and CanCurrentUserManageGroup eq true";
            reportFileName = "GroupMember.csv";

            _makeSPSafeAjax(reportUrl, {
                method: "GET",
                beforeSend: function () {
                    waitingDialog = SP.UI.ModalDialog.showWaitScreenWithNoClose("Generating...", "this may take a while ...", 150, 400);
                },
                success: function (data) {
                    _reportJSON = [];
                    if (data.d && data.d.results) {

                        $(data.d.results).each(function (i, obj) {
                            var json = _prepReportJSON(obj, _GROUP);
                            if (json) {
                                _reportJSON.push(json);
                            }
                        });

                        JSONToCSVReport(_reportJSON, _GROUP, reportFileName, true);
                    }
                },
                error: errorHandler,
                complete: function () {
                    waitingDialog.close();
                }
            });

            break;
        case _WEB:
            reportFileName = "AccessMatrix.csv";
            _reportJSON = [];
            var getWebPermissionTask = function () {
                var def = $.Deferred();
                var webTitle = _spPageContextInfo.webTitle;
                _makeSPSafeAjax("/Web/RoleAssignments?$expand=Member,RoleDefinitionBindings", {
                    method: "GET",
                    success: function (data) {
                        console.log('web...');
                        if (data.d && data.d.results) {
                            $(data.d.results).each(function (i, obj) {
                                var json = _prepReportJSON(obj, _WEB, webTitle);
                                if (json) {
                                    _reportJSON.push(json);
                                }
                            });
                        }
                    },
                    error: errorHandler,
                    complete: function () { def.resolve(); }
                });
                return def.promise();
            }

            var getListPermissionTask = function () {
                var def = $.Deferred();
                _makeSPSafeAjax("/Web/Lists?$expand=RoleAssignments/Member,RoleAssignments/RoleDefinitionBindings", {
                    method: "GET",
                    success: function (data) {
                        if (data.d && data.d.results) {
                            console.log('list...');
                            $(data.d.results).each(function (i, obj) {
                                if (obj.RoleAssignments.results) {
                                    $(obj.RoleAssignments.results).each(function (j, roleAssignement) {
                                        var json = _prepReportJSON(roleAssignement, _LIST, obj.Title);
                                        if (json) {
                                            _reportJSON.push(json);
                                        }
                                    });
                                }
                                else {
                                    var json = _prepReportJSON("", _LIST, obj.Title);
                                    _reportJSON.push(json);
                                }

                            });
                        }
                    },
                    error: errorHandler,
                    complete: function () { def.resolve(); }
                });
                return def.promise();
            }

            //Generate Report when all ajax are done.
            waitingDialog = SP.UI.ModalDialog.showWaitScreenWithNoClose("Generating...", "this may take a while ...", 150, 400);
            $.when(getWebPermissionTask(), getListPermissionTask()).done(function () {
                waitingDialog.close();
                JSONToCSVReport(_reportJSON, _WEB, reportFileName, true);
            });
            break;
    }

}

function _prepReportJSON(json, type, title) {
    var reportJson = {};
    switch (type) {
        //GroupMember Report
        case _GROUP:
            reportJson.Id = json.Id;
            reportJson.Title = json.Title;
            reportJson.GroupOwner = json.OwnerTitle;
            //reportJson.CanManage = group.CanCurrentUserManageGroup ? "Yes" : "No";
            reportJson.Members = json.Users;
            break;

            //AccessMatrix Report - Webs/
        case _WEB:
            if (json) {
                var roleDefinitionBindings = $(json.RoleDefinitionBindings.results).filter(function (i, o) { return o.Name != "Limited Access" });;
                if (roleDefinitionBindings.length > 0) {
                    reportJson.Type = "Web";
                    reportJson.Title = title;
                    reportJson.Principal = json.Member.LoginName;
                    reportJson.Permissions = { results: roleDefinitionBindings };
                }
                else {
                    //return null for limited access only json
                    return null;
                }
            }
            else {
                reportJson.Type = "Web";
                reportJson.Title = title;
                reportJson.Principal = "";
                reportJson.Permissions = "";
            }
            break;

            //AccessMatrix Report - Lists/
        case _LIST:
            if (json) {
                var roleDefinitionBindings = $(json.RoleDefinitionBindings.results).filter(function (i, o) { return o.Name != "Limited Access" });;
                if (roleDefinitionBindings.length > 0) {
                    reportJson.Type = "List";
                    reportJson.Title = title;
                    reportJson.Principal = json.Member.LoginName;
                    reportJson.Permissions = { results: roleDefinitionBindings };
                }
                else {
                    //return null for limited access only json
                    return null;
                }
            }
            else {
                reportJson.Type = "List";
                reportJson.Title = title;
                reportJson.Principal = "";
                reportJson.Permissions = "";//{results: roleDefinitionBindings};

            }
            break;
    }
    return reportJson;
}

function GetSiteGroupsByUserId(userId, name, loginName, uiId) {
    var url = appWebUrl + "/_api/Web/GetUserById(" + userId + ")/Groups";
    $.ajax({
        url: url,
        method: "GET",
        headers: { "accept": "application/json; odata=verbose" },
        beforeSend: function () {
            _showLoadingPanel(uiId);
        },
        success: function (data) {
            if (data.d && data.d.results) {
                var html = _generateGroupTable(data.d.results);
                _openPopupWithHTML(html[0], "Group Membership - " + name);
            }
        },
        error: errorHandler,
        complete: function () {
            _removeLoadingPanel(uiId);
        }
    });
}

function ShowFullScreenSuggestion() {
    var cancelButton = "<button onclick='SP.UI.ModalDialog.commonModalDialogClose(0);'>Cancel</button>";
    var okButton = "<button onclick='SP.UI.ModalDialog.commonModalDialogClose(1);'>Ok</button>";
    var content = "<p>Easy User Manager works better in full screen mode. Click ok to toggle Full Screen mode?</p><br><a></a>";
    var html = document.createElement('html');
    html.innerHTML = content + "<div style='text-align:center'>" + okButton + cancelButton + "</div>";
    html.className = 'ms-dialog';
    var callback = function (dialogResult) {
        if (dialogResult == SP.UI.DialogResult.OK) {
            SetFullScreenMode(true);
        }
    }

    OpenPopUpPageWithDialogOptions({
        html: html,
        title: "Toggle to Full Screen mode?",
        showClose: true,
        height: 120,
        width: 400,
        dialogReturnValueCallback: callback
    });
}

function errorHandler(e, errorCode, errorMessage) {
    var error = [e, errorCode, errorMessage];
    console.log(error);
    _logConsole(errorMessage ? errorMessage : e.responseJSON.error.message.value);
}


function getQueryStringParameter(paramToRetrieve) {
    if (document.URL.split("?").length < 2) {
        return;
    }
    var params = document.URL.split("?")[1].split("&");
    var strParams = "";
    for (var i = 0; i < params.length; i = i + 1) {
        var singleParam = params[i].split("=");
        if (singleParam[0] == paramToRetrieve)
            return singleParam[1];
    }
}


/** REPORT JS **/
/***************/

function JSONToCSVReport(JSONData, type, reportFileName, showLabel) {
    var arrData = typeof JSONData != 'object' ? JSON.parse(JSONData) : JSONData;
    var CSV = '';

    switch (type) {
        case 0:
            CSV = _convertJSONtoGroupMemberReport(arrData, showLabel);
            break;
        case 1:
            CSV = _convertJSONtoAccessMatrixReport(arrData, showLabel);
            break;
    }

    if (CSV == '') {
        alert("Invalid data");
        return;
    }

    if (window.navigator.msSaveOrOpenBlob) {
        var blobObject = new Blob([CSV]);
        window.navigator.msSaveOrOpenBlob(blobObject, reportFileName);
        return;
    } else {
        var uri = 'data:application/csv;charset=utf-8,' + escape(CSV);
        var link = document.createElement("a");
        link.href = uri;
        link.style = "visibility:hidden";
        link.download = reportFileName;
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
    }
}

function _convertJSONtoGroupMemberReport(arrData, showLabel) {
    var csvString = '';
    if (showLabel) {
        var row = "";
        for (var index in arrData[0]) {
            row += index + ',';
        }
        row = row.slice(0, -1);
        csvString += row + '\r\n';
    }

    for (var i = 0; i < arrData.length; i++) {
        var row = "";
        var hasMultipleMembers = false;
        for (var index in arrData[i]) {
            var arrValue = "";
            if (typeof (arrData[i][index]) == 'object' && index == "Members") {
                var users = arrData[i][index].results;
                var userValue = '';
                if (users.length > 1) {
                    hasMultipleMembers = true;
                }

                for (var j = 0; j < users.length; j++) {
                    userValue = (users[j].LoginName == null ? '""' : '"' + users[j].LoginName + '"');
                    if (hasMultipleMembers) {
                        var curRow = row + userValue + '\r\n';
                        csvString += curRow;
                    }
                }

                if (!hasMultipleMembers) {
                    arrValue = userValue;
                }
            }
            else {
                arrValue = arrData[i][index] == null ? "" : '"' + arrData[i][index] + '"';
            }

            if (!hasMultipleMembers) {
                row += arrValue + ',';
            }
        }

        if (!hasMultipleMembers) {
            row = row.substring(0, row.length - 1);
            csvString += row + '\r\n';
        }
    }

    return csvString;
}

function _convertJSONtoAccessMatrixReport(arrData, showLabel) {
    var csvString = '';
    if (showLabel) {
        var row = "";
        for (var index in arrData[0]) {
            row += index + ',';
        }
        row = row.slice(0, -1);
        csvString += row + '\r\n';
    }

    for (var i = 0; i < arrData.length; i++) {
        var row = "";
        var hasMultiplePermissions = false;
        for (var index in arrData[i]) {
            var arrValue = "";
            if (typeof (arrData[i][index]) == 'object' && index == "Permissions") {
                var roleBindings = arrData[i][index].results;
                var permissionValue = '';
                if (roleBindings.length > 1) {
                    hasMultiplePermissions = true;
                }

                for (var j = 0; j < roleBindings.length; j++) {
                    permissionValue = '"' + roleBindings[j].Name + '"';
                    if (hasMultiplePermissions) {
                        var curRow = row + permissionValue + '\r\n';
                        csvString += curRow;
                    }
                }

                if (!hasMultiplePermissions) {
                    arrValue = permissionValue;
                }
            }
            else {
                arrValue = arrData[i][index] == null ? "" : '"' + arrData[i][index] + '"';
            }

            if (!hasMultiplePermissions) {
                row += arrValue + ',';
            }
        }

        if (!hasMultiplePermissions) {
            row = row.substring(0, row.length - 1);
            csvString += row + '\r\n';
        }
    }

    return csvString;
}